package com.googlecode.stockhamster.stockmarket;

import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.List;

import com.googlecode.stockhamster.Constant;
import com.googlecode.stockhamster.bank.IBank;
import com.googlecode.stockhamster.stockstat.IStockStat;
import com.googlecode.stockhamster.utils.XFireStub;

public class StockMarketImpl implements IStockMarket {
	private static Object lock = new Object();

	private IBank bank;
	private IStockStat status;

	public StockMarketImpl() throws MalformedURLException {
		bank = new XFireStub<IBank>(Constant.bankUrl, IBank.class).getStub();
		status = new XFireStub<IStockStat>(Constant.stockStatUrl,
				IStockStat.class).getStub();
	}

	private String genCode(String userName) {
		StringBuffer code = new StringBuffer();
		for (int i = 0; i < 8; i++)
			code.append((char) ('a' + (int) (Math.random() * 26)));
		return code.toString();
	}

	/**
	 * setup an account
	 * 
	 * @param username
	 * @return password(code) for the user , null for existing user
	 */
	@Override
	public String open(String username) {
		if (Account.get(username) != null) {
			return null;
		}
		Account newAccount = new Account(username, genCode(username));
		return newAccount.insert() ? newAccount.getCode() : null;
	}

	@Override
	public Boolean close(String username, String code) {
		Account user = Account.get(username);
		if (user == null || !user.getCode().equals(code))
			return false;
		if (!user.delete())
			return false;

		AccountStock[] userAccountStockList = AccountStock.get(username);
		for (int i = 0; i < userAccountStockList.length; i++)
			if (!userAccountStockList[i].delete())
				return false;

		TradeItem[] userTradeList = TradeItem.get(username);
		for (int i = 0; i < userTradeList.length; i++)
			if (!userTradeList[i].delete())
				return false;

		return true;
	}

	@Override
	public Integer getStockAmount(String username, String code, Integer stockID) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return null;

			AccountStock userAccountStock = AccountStock.get(username, stockID);
			if (userAccountStock == null)
				return 0;

			return userAccountStock.amount;
		}
	}

	@Override
	public Integer[] getStockList(String username, String code) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return null;

			AccountStock[] userAccountStockList = AccountStock.get(username);
			List<Integer> resultList = new ArrayList<Integer>();
			for (int i = 0; i < userAccountStockList.length; i++)
				resultList.add(userAccountStockList[i].getStockID());

			Integer[] targetList = new Integer[resultList.size()];
			for (int i = 0; i < targetList.length; i++)
				targetList[i] = resultList.get(i);

			return targetList;
		}
	}

	@Override
	public Boolean removeRequestBuy(String username, String code,
			Integer stockID, Long price) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return false;

			TradeItem userTradeItem = TradeItem.get(true, username, price);
			if (userTradeItem == null)
				return false;

			bank.sendBack(userTradeItem.getMoneyCode());

			return userTradeItem.delete();
		}
	}

	@Override
	public Boolean removeRequestSell(String username, String code,
			Integer stockID, Long price) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return false;

			TradeItem userTradeItem = TradeItem.get(false, username, price);
			if (userTradeItem == null)
				return false;

			incStockHolding(username, stockID, userTradeItem.amount);

			return userTradeItem.delete();
		}
	}

	@Override
	public Boolean requestBuy(String username, String code, Integer stockID,
			Long price, Integer amount, String moneyCode) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return false;

			if (amount == 0)
				return false;

			TradeItem[] sellList = TradeItem.get(false, stockID);
			Long lastTradePrice = null;
			int tradeTotal = 0;

			for (int i = 0; i < sellList.length && amount > 0
					&& price >= sellList[i].getPrice(); i++) {
				int tradeAmount = Math.min(amount, sellList[i].amount);
				String leftCheque = bank.transfer(sellList[i].getUsername(),
						moneyCode, tradeAmount * sellList[i].getPrice());
				if (leftCheque == null) {
					break;
				}

				sellList[i].amount -= tradeAmount;
				amount -= tradeAmount;
				tradeTotal += tradeAmount;
				lastTradePrice = sellList[i].getPrice();

				moneyCode = leftCheque;

				if (sellList[i].amount == 0)
					sellList[i].delete();
				else
					sellList[i].save();
			}

			if (lastTradePrice != null)
				status.updatePrice(Constant.statusAdminCode, stockID,
						lastTradePrice, (long) tradeTotal);

			if (amount > 0) {
				TradeItem newTradeItem = TradeItem.get(true, username, stockID,
						price);
				if (newTradeItem == null) {
					newTradeItem = new TradeItem(true, username, stockID,
							price, amount, moneyCode);
					newTradeItem.insert();
				} else {
					newTradeItem.amount += amount;
					newTradeItem.save();
				}
			} else {
				if (!moneyCode.equals(""))
					bank.sendBack(moneyCode);
			}

			return incStockHolding(username, stockID, tradeTotal);
		}
	}

	@Override
	public Boolean requestSell(String username, String code, Integer stockID,
			Long price, Integer amount) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return false;

			if (amount == 0)
				return false;

			AccountStock userAccountStock = AccountStock.get(username, stockID);
			if (userAccountStock == null || amount > userAccountStock.amount)
				return false;

			decStockHolding(username, stockID, amount);

			TradeItem[] buyList = TradeItem.get(true, stockID);
			Long lastTradePrice = null;
			int tradeTotal = 0;

			for (int i = buyList.length - 1; i >= 0 && amount > 0; i--)
				if (buyList[i].getPrice() >= price) {
					int tradeAmount = Math.min(amount, buyList[i].amount);
					String leftCheque = bank.transfer(username, buyList[i]
							.getMoneyCode(), tradeAmount
							* buyList[i].getPrice());
					if (leftCheque == null) {
						bank.sendBack(buyList[i].getMoneyCode());
						buyList[i].delete();
						continue;
					}

					buyList[i].amount -= tradeAmount;
					amount -= tradeAmount;
					tradeTotal += tradeAmount;
					lastTradePrice = buyList[i].getPrice();

					incStockHolding(buyList[i].getUsername(), stockID,
							tradeAmount);

					if (buyList[i].amount == 0) {
						if (!leftCheque.equals(""))
							bank.sendBack(leftCheque);
						buyList[i].delete();
					} else {
						buyList[i].setMoneyCode(leftCheque);
						buyList[i].save();
					}
				}

			if (lastTradePrice != null)
				status.updatePrice(Constant.statusAdminCode, stockID,
						lastTradePrice, (long) tradeTotal);

			if (amount > 0) {
				TradeItem newTradeItem = TradeItem.get(false, username,
						stockID, price);
				if (newTradeItem == null) {
					newTradeItem = new TradeItem(false, username, stockID,
							price, amount, "");
					newTradeItem.insert();
				} else {
					newTradeItem.amount += amount;
					newTradeItem.save();
				}
			}

			return true;
		}
	}

	@Override
	public Boolean removeUserRequest(String username, String code) {
		synchronized (lock) {
			Account user = Account.get(username);
			if (user == null || !user.getCode().equals(code))
				return false;

			TradeItem[] tradeList = TradeItem.get(username);

			boolean flag = true;
			for (int i = 0; i < tradeList.length; i++) {
				if (!tradeList[i].getIsBuy()) {
					incStockHolding(tradeList[i].getUsername(), tradeList[i]
							.getStockID(), tradeList[i].amount);
				}
				if (!tradeList[i].delete())
					flag = false;
			}

			return flag;
		}
	}

	// for administration
	/*
	 * public Boolean setCompanyStock(String adminCode, String companyName,
	 * Integer stockID, Integer amount) { if
	 * (adminCode.equals(Constant.marketAdminCode)) { AccountStock tmp = new
	 * AccountStock(companyName, stockID, amount); return tmp.insert(); } return
	 * false; }
	 */
	@Override
	public Boolean addMarketStock(String adminCode, Integer stockID,
			Integer amount) {
		synchronized (lock) {
			if (adminCode.equals(Constant.marketAdminCode)) {
				AccountStock tmp = new AccountStock("market", stockID, amount);
				return tmp.insert();
			}
			return false;
		}
	}

	@Override
	public String[] getStockOwner(String adminCode, Integer stockID) {
		synchronized (lock) {
			if (adminCode.equals(Constant.marketAdminCode)) {
				return AccountStock.getStockOwner(stockID);
			}
			return null;
		}
	}

	@Override
	public Integer getStockAmountByAdmin(String adminCode, String username,
			Integer stockID) {
		synchronized (lock) {
			if (adminCode.equals(Constant.marketAdminCode)) {
				Account user = Account.get(username);
				if (user == null)
					return null;
				AccountStock userAccountStock = AccountStock.get(username,
						stockID);
				Integer amount = userAccountStock == null ? 0
						: userAccountStock.amount;
				TradeItem items[] = TradeItem.get(false, username, stockID);
				for (TradeItem item : items)
					amount += item.amount;
				return amount;
			}
			return null;
		}
	}

	// for debug
	public Boolean printUserQueue(String username) {
		System.out.println("Information of " + username
				+ "------------------------");
		AccountStock[] userAccountStock = AccountStock.get(username);

		for (int i = 0; i < userAccountStock.length; i++) {
			System.out.println("stockID =" + userAccountStock[i].getStockID()
					+ " amount =" + userAccountStock[i].amount);
		}

		System.out.println("Trade list:");
		TradeItem[] tradeList = TradeItem.get(username);
		for (int i = 0; i < tradeList.length; i++)
			System.out.println("isBuy =" + tradeList[i].getIsBuy()
					+ " stockID=" + tradeList[i].getStockID() + " price="
					+ tradeList[i].getPrice() + " amount="
					+ tradeList[i].amount);

		return true;
	}

	private boolean incStockHolding(String username, int stockID, int delta) {
		if (delta == 0)
			return true;

		AccountStock account = AccountStock.get(username, stockID);
		if (account == null) {
			account = new AccountStock(username, stockID, delta);
			return account.insert();
		} else {
			account.amount += delta;
			return account.save();
		}
	}

	private boolean decStockHolding(String username, int stockID, int delta) {
		if (delta == 0)
			return true;

		AccountStock account = AccountStock.get(username, stockID);
		if (account == null)
			return false;
		account.amount -= delta;
		if (account.amount == 0) {
			return account.delete();
		} else {
			return account.save();
		}
	}

}
